package rtsp_server

import (
	"context"
	"fmt"
	"log"
	"sync"

	"github.com/bluenviron/gortsplib/v3"
	"github.com/bluenviron/gortsplib/v3/pkg/base"
)

type Server struct {
	Handler  *serverHandler
	port     int
	rtpPort  int
	rtcpPort int
}

// NewServer 554, 8000, 8001
func NewServer(port int, rtpPort int, rtcpPort int) *Server {
	return &Server{
		Handler:  NewServerHandler(),
		port:     port,
		rtpPort:  rtpPort,
		rtcpPort: rtcpPort,
	}
}

func (tis *Server) Serve() error {

	// configure the server
	s := &gortsplib.Server{
		Handler:           tis.Handler,
		RTSPAddress:       fmt.Sprintf(":%v", tis.port),
		UDPRTPAddress:     fmt.Sprintf(":%v", tis.rtpPort),
		UDPRTCPAddress:    fmt.Sprintf(":%v", tis.rtcpPort),
		MulticastIPRange:  "224.1.0.0/16",
		MulticastRTPPort:  tis.rtpPort + 2,
		MulticastRTCPPort: tis.rtpPort + 3,
	}

	// start server and wait until a fatal error
	log.Println(s.StartAndWait())
	return nil
}

func (tis *Server) GetSessionList() []string {
	return tis.Handler.getSessionList()
}

type serverHandler struct {
	mutex sync.Mutex

	sessionMutex   sync.RWMutex
	sessionMap     map[string]*session
	OnPush, OnPull func(ctx context.Context, path string)
}

func NewServerHandler() *serverHandler {
	return &serverHandler{
		sessionMap: map[string]*session{},
		OnPull:     func(context.Context, string) {},
		OnPush:     func(context.Context, string) {},
	}
}

func (sh *serverHandler) getSessionList() []string {
	sh.sessionMutex.Lock()
	defer sh.sessionMutex.Unlock()

	var pathList []string
	for path, _ := range sh.sessionMap {
		pathList = append(pathList, path)
	}

	return pathList
}

func (sh *serverHandler) OnConnOpen(ctx *gortsplib.ServerHandlerOnConnOpenCtx) {
	log.Printf("conn opened")
}

func (sh *serverHandler) OnConnClose(ctx *gortsplib.ServerHandlerOnConnCloseCtx) {
	log.Printf("conn closed (%v)", ctx.Error)
}

func (sh *serverHandler) OnSessionOpen(ctx *gortsplib.ServerHandlerOnSessionOpenCtx) {
	log.Printf("session opened")
}

func (sh *serverHandler) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionCloseCtx) {
	log.Printf("session closed")

	sh.mutex.Lock()
	defer sh.mutex.Unlock()

	for _, sess := range sh.sessionMap {
		if ctx.Session == sess.publisher {
			sess.Close()
			delete(sh.sessionMap, sess.path)

			break
		}
	}
}

func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
	log.Printf("describe request")

	sh.mutex.Lock()
	defer sh.mutex.Unlock()

	sess, ok := sh.sessionMap[ctx.Path]
	if !ok {
		log.Println("not found session ", ctx.Path)
		return &base.Response{
			StatusCode: base.StatusNotFound,
		}, nil, nil
	}

	// send the track list that is being published to the client
	return &base.Response{
		StatusCode: base.StatusOK,
	}, sess.stream, nil
}

func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
	log.Printf("announce request")

	sh.mutex.Lock()
	defer sh.mutex.Unlock()

	sess := newSession(ctx)
	sh.sessionMap[sess.path] = sess

	return &base.Response{
		StatusCode: base.StatusOK,
	}, nil
}

func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
	log.Printf("setup request")

	sess, ok := sh.sessionMap[ctx.Path]
	if !ok {
		log.Println("not found session ", ctx.Path)
		return &base.Response{
			StatusCode: base.StatusNotFound,
		}, nil, nil
	}

	return &base.Response{
		StatusCode: base.StatusOK,
	}, sess.stream, nil
}

func (sh *serverHandler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
	log.Printf("play request")

	sh.OnPull(context.Background(), ctx.Path)

	return &base.Response{
		StatusCode: base.StatusOK,
	}, nil
}

func (sh *serverHandler) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
	log.Printf("record request")

	for _, sess := range sh.sessionMap {
		if ctx.Session == sess.publisher {
			sess.OnRecord(ctx)
			sh.OnPush(sess.ctx, sess.path)
		}
	}

	return &base.Response{
		StatusCode: base.StatusOK,
	}, nil
}
